//////////////////////////////////////////////////
// EASIM project
// easim.net.Vector
// 6/20/2007 1:14 PM
// Cong Liu
// gzcong@gmail.com

// Copyright (C) 2007
// All rights reserved.
// Refer to LICENSE of JIST (Java In Simulation Time) for terms and conditions of use.

package easim.omar;

import easim.net.DtnNodeInterface;
import easim.net.Clock;
import easim.net.Timer;

import jist.swans.Constants;
import jist.swans.misc.Message;
import jist.runtime.JistAPI.DoNotRewrite;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Iterator;

public class Vector implements DtnNodeInterface, DoNotRewrite
{

  public static final long INIT_TIME = - 1000 * Constants.SECOND;
  public static final long CLEANER_INTERVAL = 10 * Constants.SECOND;
    
  /** class Vector */
  protected static class VectorItem implements DoNotRewrite
  {
    public int srcId = -1;
    public int msgId = -1;
    public long addTime = INIT_TIME;
    public long timeout = INIT_TIME;

    public String toString() {
      return srcId + "-" + msgId + "(" + timeout + ")";
    }
    public boolean equals(Object obj) {
      if (! (obj instanceof VectorItem)) return false;
      VectorItem vector = (VectorItem)obj;
      return (srcId == vector.srcId && msgId == vector.msgId);
    }
    public int hashCode() {
      return (msgId * 1000 + srcId);
    }
    public void copyTo(VectorItem vector) {
      vector.srcId = srcId;
      vector.msgId = msgId;
      vector.addTime = addTime;
      vector.timeout = timeout;
    }
    public VectorItem getClone() {
      VectorItem vector = new VectorItem();
      copyTo(vector);
      return vector;
    }
  } // end class Vector

  /** class NodeInfo */
  protected static class NodeInfo implements DoNotRewrite
  {
    public int id = -1;
    public long lastUpdateSentTime = INIT_TIME;
    public long lastUpdateRecvTime = INIT_TIME;
    public LinkedList list = new LinkedList();
    public HashSet set = new HashSet();
    public long minTimeout = INIT_TIME;
  } // end class NodeInfo

  /** class ContactInfo */
  protected static class ContactInfo implements DoNotRewrite
  {
    public int id = -1;
    public long encounterTime = INIT_TIME;
  } // end class ContactInfo

  /** class UpdateMessage */
  public static class UpdateMessage implements Message, DoNotRewrite
  {
    public long time;
    public VectorItem[] vectors;
    public int getSize() {
      return 8 + 16 * vectors.length;
    }
    public void getBytes(byte[] msg, int offset) {
      throw new RuntimeException("no implementation");
    }
    // for rollback, not part of the message exactly.
    public HashMap rbInfo;
  } // end class UpdateMessage

  /** class Cleaner */
  public class Cleaner implements Timer, DoNotRewrite
  {
    public void start() {
      Clock.start(this, CLEANER_INTERVAL, false);
    }
    public void timeout() {
      cleanVector();
      start();
    }
  } // end class Cleaner

  ///////////////////////////
  // variables
  //

  protected Node node;
  protected HashMap nodes = new HashMap();
  protected HashMap contacts = new HashMap();
  protected Cleaner cleaner = new Cleaner();

  public Vector(Node node) {
    this.node = node;
    cleaner.start();
  }

  public void onContactDetected(int contactId) {
    node.startJitterTimer();
    ContactInfo contact = new ContactInfo();
    contact.id = contactId;
    contact.encounterTime = Clock.getTime();
    contacts.put(new Integer(contact.id), contact);
    if (! nodes.containsKey(new Integer(contactId))) {
      NodeInfo info = new NodeInfo();
      info.id = contactId;
      nodes.put(new Integer(info.id), info);
    }
  }

  public void onContactBroken(int contactId) {
    contacts.remove(new Integer(contactId));
  }

  public void receiveMessage(Message message, int srcId) {
    UpdateMessage uMsg = (UpdateMessage)message;
    long curTime = Clock.getTime();
    NodeInfo info = (NodeInfo)nodes.get(new Integer(srcId));
    info.lastUpdateRecvTime = curTime;
    incrementalUpdate(info, uMsg.vectors, uMsg.time);
  }

  public Message nextMessageToSend() {
    long curTime = Clock.getTime();
    UpdateMessage uMsg = new UpdateMessage();
    uMsg.time = curTime;
    long lastUpdateTime = curTime;
    Iterator it = contacts.values().iterator();
    boolean needToSend = false;
    while (it.hasNext()) {
      ContactInfo contact = (ContactInfo)it.next();
      NodeInfo info = (NodeInfo)nodes.get(new Integer(contact.id));
      if (lastUpdateTime > info.lastUpdateSentTime) {
        lastUpdateTime = info.lastUpdateSentTime;
      }
      if (contact.encounterTime > info.lastUpdateSentTime) {
        needToSend = true;
      }
    }
    if (lastUpdateTime > node.data.updateTime && (! needToSend)) {
      return null;
    }
    LinkedList toSend = new LinkedList();
    long latestUpdateTime = lastUpdateTime;
    Data.DataItem[] datas = node.data.datas;
    for (int i = 0; i < datas.length; ++ i) {
      if (datas[i] == null) continue;
      if (datas[i].timeout <= curTime) continue;
      if (datas[i].tickets == 0) continue;
      if (datas[i].addTime < lastUpdateTime) continue;
      VectorItem vector = new VectorItem();
      vector.srcId = datas[i].srcId;
      vector.msgId = datas[i].msgId;
      vector.timeout = datas[i].timeout;
      if (latestUpdateTime < vector.addTime) {
        latestUpdateTime = vector.addTime;
      }
      toSend.addLast(vector);
    }
    if (toSend.size() == 0 && (! needToSend)) {
      return null;
    }
    uMsg.vectors = (VectorItem[])toSend.toArray(new VectorItem[0]);
    for (int i = 0; i < uMsg.vectors.length - 1; ++ i) {
      boolean updated = false;
      for (int j = uMsg.vectors.length - 2; j >= i; -- j) {
        if (uMsg.vectors[j].timeout > uMsg.vectors[j + 1].timeout) {
          updated = true;
          VectorItem temp = uMsg.vectors[j];
          uMsg.vectors[j] = uMsg.vectors[j + 1];
          uMsg.vectors[j + 1] = temp;
        }
      }
      if (! updated) break;
    }
    uMsg.rbInfo = new HashMap();
    it = contacts.values().iterator();
    while (it.hasNext()) {
      ContactInfo contact = (ContactInfo)it.next();
      NodeInfo info = (NodeInfo)nodes.get(new Integer(contact.id));
      if (info.lastUpdateSentTime <= latestUpdateTime ||
          info.lastUpdateSentTime < contact.encounterTime) {
        uMsg.rbInfo.put(new Integer(info.id), new Long(info.lastUpdateSentTime));
        info.lastUpdateSentTime = curTime;
      }
    }
    ++ Stats.send.vectorMsgs;
    Stats.send.vectorMsgSize += uMsg.getSize();
    //System.out.println(toString(node.getId(), uMsg.vectors));
    return uMsg;
  }

  public void onAckMessage(Message message) {
    UpdateMessage uMsg = (UpdateMessage)message;
    ++ Stats.recv.vectorMsgs;
    Stats.recv.vectorMsgSize += uMsg.getSize();
  }

  /** rollback the update information */
  public void onDropMessage(Message message, int[] dstIds) {
    UpdateMessage uMsg = (UpdateMessage)message;
    boolean dropped = false;
    for (int i = 0; i < dstIds.length; ++ i) {
      int unacked = dstIds[i];
      Long lastUpdateSentTime = (Long)uMsg.rbInfo.get(new Integer(unacked));
      if (lastUpdateSentTime == null) continue;
      NodeInfo info = (NodeInfo)nodes.get(new Integer(unacked));
      info.lastUpdateSentTime = lastUpdateSentTime.longValue();
      dropped = true;
    }
    if (! dropped) {
      onAckMessage(message);
    }
  }

  public int cleanVector() {
    int size = 0;
    long curTime = Clock.getTime();
    Iterator it = nodes.keySet().iterator();
    while (it.hasNext()) {
      Integer id = (Integer)it.next();
      NodeInfo info = (NodeInfo)nodes.get(id);
      while (info.list.size() > 0) {
        VectorItem vector = (VectorItem)info.list.getFirst();
        if (vector.timeout > curTime) break;
        info.list.removeFirst();
        info.set.remove(vector);
      }
      size += info.list.size();
    }
    return size;
  }

  /** update the mirror table of the neighbor */
  public void incrementalUpdate(NodeInfo info, VectorItem[] updates, long srcTime) {
    long curTime = Clock.getTime();
    int count = updates.length;
    LinkedList tail = new LinkedList();
    while (count > 0 && info.list.size() > 0) {
      VectorItem vector1 = (VectorItem)info.list.getLast();
      VectorItem vector2 = updates[count - 1];
      if (info.set.contains(vector2)) {
        -- count;
        continue;
      }
      if (vector1.timeout <= curTime) {
        info.list.removeLast();
        info.set.remove(vector1);
        continue;
      }
      long timeout = vector2.timeout + (curTime - srcTime);
      if (timeout <= curTime) {
        count = 0;
        break;
      }
      if (vector1.timeout <= timeout) {
        info.list.removeLast();
        tail.addFirst(vector1);
        continue;
      }
      else {
        -- count;
        vector2 = vector2.getClone();
        vector2.addTime = curTime;
        vector2.timeout = timeout;
        tail.addFirst(vector2);
        info.set.add(vector2);
        continue;
      }
    }
    while (count > 0) {
      VectorItem vector2 = updates[count - 1].getClone();
      -- count;
      if (info.set.contains(vector2)) continue;
      vector2.addTime = curTime;
      vector2.timeout = vector2.timeout + (curTime - srcTime);
      info.list.addFirst(vector2);
      info.set.add(vector2);
    }
    while (tail.size() > 0) {
      VectorItem vector2 = (VectorItem)tail.removeFirst();
      info.list.addLast(vector2);
    }
  }

  public boolean containsVector(int contactId, int msgSrcId, int msgId) {
    NodeInfo info = (NodeInfo)nodes.get(new Integer(contactId));
    if (info == null) return false;
    VectorItem temp = new VectorItem();
    temp.srcId = msgSrcId;
    temp.msgId = msgId;
    return info.set.contains(temp);
  }

  public void addVector(int contactId, int msgSrcId, int msgId, long timeout) {
    NodeInfo info = (NodeInfo)nodes.get(new Integer(contactId));
    if (info == null) {
      info = new NodeInfo();
      info.id = contactId;
      nodes.put(new Integer(info.id), info);
    }
    long curTime = Clock.getTime();
    VectorItem toAdd = new VectorItem();
    toAdd.srcId = msgSrcId;
    toAdd.msgId = msgId;
    toAdd.timeout = timeout;
    toAdd.addTime = curTime;
    incrementalUpdate(info, new VectorItem[] {toAdd}, curTime);
  }

  public void removeVector(int contactId, int msgSrcId, int msgId) {
    NodeInfo info = (NodeInfo)nodes.get(new Integer(contactId));
    if (info == null) return;
    VectorItem temp = new VectorItem();
    temp.srcId = msgSrcId;
    temp.msgId = msgId;
    info.list.remove(temp);
    info.set.remove(temp);
  }

  public boolean updateReceived(int contactId) {
    NodeInfo info = (NodeInfo)nodes.get(new Integer(contactId));
    if (info == null) return false;
    ContactInfo contact = (ContactInfo)contacts.get(new Integer(contactId));
    if (contact == null) return false;
    return (info.lastUpdateRecvTime > contact.encounterTime);
  }

  public String toString() {
    String buf = "Vector=\r\n";
    int count = 0;
    int i = 0;
    while (count < nodes.size()) {
      NodeInfo info = (NodeInfo)nodes.get(new Integer(i));
      if (info != null) {
        ++ count;
        if (info.list.size() > 0) {
          Iterator it = info.list.iterator();
          while (it.hasNext()) {
            VectorItem vector = (VectorItem)it.next();
            buf += " " + vector;
          }
          buf += "\r\n";
        }
      }
      ++ i;
    }
    return buf;
  }

  public static String toString(int id, VectorItem[] vectors) {
    String buf = "[" + id + "] Vector=\r\n";
    for (int i = 0; i < vectors.length; ++ i) {
      buf += vectors[i] + "\r\n";
    }
    return buf;
  }

}
